#!/usr/bin/env python
#

import os, sys, logging
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

from google.appengine.dist import use_library
use_library('django', '1.2')

import logging
import urllib
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp import util
from mapreduce.lib import files
from google.appengine.api import taskqueue
from google.appengine.api import users
from google.appengine.api import app_identity
from google.appengine.api import memcache
from mapreduce import base_handler
from mapreduce import mapreduce_pipeline
from mapreduce.lib import files
from mapreduce import shuffler
from mapreduce import operation as op
import time
import models
import datetime
import utils
import simplejson
import ytapi_helper

from basehandler import BaseAdminHandler


class AddPartners(webapp.RequestHandler):
  def get(self):
    organization = models.Organization.get(self.request.get('org_key'))
    partners = models.PartnerAssociation.all().filter(
        'organization', organization).fetch(1000)
    template_values = {'organization': organization,
                       'partners': partners}
    self.response.out.write(template.render(
        'templates/edit_partners.html', template_values))

  def post(self):
    """Used as task queue handler."""
    channel_names = self.request.get('channel_names')
    action = self.request.get('action', 'add')

    organization = models.Organization.get(self.request.get('org_key'))
    for channel_name in set(channel_names.split('\r\n')):
      channel_name = channel_name.strip()
      key_name = '%s_%s' % (organization.key(), channel_name)
      if action == 'add':
        partners = models.PremiumPartners.get_or_insert(
              channel_name,
              channel_name=channel_name)
        if organization:
          models.PartnerAssociation().get_or_insert(
              key_name,
              partners=partners, organization=organization).put()
      elif action == 'delete':
        #delete partner
        association = models.PartnerAssociation().get_by_key_name(
            key_name)
        models.db.delete(association)
        #delete delete partner from db incase another campaign has it mapped?
        #perhaps check if non are mapped first?

    memcache.delete('%s_partners' % organization.org_key())
    self.response.headers['Content-Type'] = 'application/json; charset=utf-8;'
    self.response.out.write(simplejson.dumps({'status': 'SUCCESS'}))


class AddPlaylists(webapp.RequestHandler):
  def get(self):
    organization = models.Organization.get(self.request.get('org_key'))
    playlists = models.PlaylistAssociation.all().filter(
        'organization', organization).fetch(1000)
    template_values = {'organization': organization,
                       'playlists': playlists}
    self.response.out.write(template.render(
        'templates/edit_playlists.html', template_values))

  def post(self):
    """Used as task queue handler."""
    playlistid = self.request.get('playlist_ids').strip()
    playlist_name = self.request.get('playlist_name').strip()
    organization = models.Organization.get(self.request.get('org_key'))
    action = self.request.get('action', 'add')
    key_name = '%s_%s' % (organization.key(), playlistid)
    if action == 'add':
      playlist = models.Playlists.get_or_insert(
          playlistid,
          playlistid=playlistid,
          playlist_name=playlist_name)
      models.PlaylistAssociation().get_or_insert(
          key_name,
          playlist=playlist, organization=organization).put()

    elif action == 'delete':
      assoc = models.PlaylistAssociation().get_by_key_name(key_name)
      if assoc:
        models.db.delete(assoc)

    memcache.delete('%s_playlists' % organization.key())
    self.response.headers['Content-Type'] = 'application/json; charset=utf-8;'
    self.response.out.write(simplejson.dumps({'status': 'SUCCESS'}))


class AddCurators(webapp.RequestHandler):
  def get(self):
    organization = models.Organization.get(self.request.get('org_key'))
    curators = models.UserAccessRights.all().filter(
        'organization', organization).fetch(1000)
    template_values = {'organization': organization,
                       'curators': curators}
    self.response.out.write(template.render(
        'templates/edit_curators.html', template_values))


  def post(self):
    curator_list = self.request.get('curators')
    organization = models.Organization.get(self.request.get('org_key'))
    action = self.request.get('action', 'add')

    for username in set(curator_list.split('\r\n')):
      username = username.strip()
      if username.endswith('@gmail.com') or username.endswith('@google.com'):
        if action == 'add':
          user = models.User.get_or_insert(
              username,
              username=username)
          if organization:
            models.UserAccessRights(
                username=user, organization=organization).put()
        elif action == 'delete':
          logging.info('trying to delete %s', username)
          user = models.User.get_by_key_name(username)
          association = models.UserAccessRights.all().filter(
              'username', user).get()
          models.db.delete(association)

    self.response.headers['Content-Type'] = 'application/json; charset=utf-8;'
    self.response.out.write(simplejson.dumps({'status': 'SUCCESS'}))


class DumpYTPlaylists(webapp.RequestHandler):
  """Dumps 200 playlistids into models.WhiteLabelPlaylistBuckets.

  This is used in dev/prod as a one off dump of all playlistsids from an account
  specifically created for this purpose.

  account details: 
  """
  YT_USERNAME = 'curatorgillette'
  ACCOUNT_USERNAME = 'gillette.curator.tool@gmail.com'
  ACCOUNT_PASSWORD = 'gillette123'
  def post(self):
    playlists = utils.FetchPlaylists(self.YT_USERNAME)
    for playlist in playlists:
      models.WhiteLabelPlaylistBuckets.get_or_insert(
          key_name=playlist['playlistid'],
          playlistid=playlist['playlistid'],
          playlist_name=playlist['title'])



class AdminIndexHandler(BaseAdminHandler):
  def __init__(self):
    BaseAdminHandler.__init__(self)

  def GetRequest(self):
    #check if there is an oauth creditental in place for YT upload
    oauth = models.OAuthUser.all().fetch(10)

    all_orgs = models.Organization.all().fetch(100)
    stats = models.UsageStats()

    template_values = {
        'oauth_user': oauth,
        'usage_stats': stats.GetAllStats(),
        'org_list': all_orgs}
    logging.info(all_orgs)
    self.Render('admin_index.html', template_values)


class Organization(BaseAdminHandler):
  """An internal only handler for googlers to setup organization and access for
  users."""
  def __init__(self):
    BaseAdminHandler.__init__(self)

  def GetRequest(self):
    action = self.request.get('action', 'add')
    if action == 'add':
      self.Render('create_organization.html', {})
    elif action == 'edit':
      organization = models.Organization.get(self.request.get('org_key'))
      playlists = models.PlaylistAssociation.all().filter(
          'organization', organization).count()
      partners = models.PartnerAssociation.all().filter(
          'organization', organization).count()
      curators = models.UserAccessRights.all().filter(
          'organization', organization).count()
      tags = models.Tags.all().filter(
          'organization', organization).count()

      template_values = {'organization': organization,
                         'partners_count': partners,
                         'playlist_count': playlists,
                         'tag_count': tags,
                         'curators_count': curators}
      self.Render('edit_organization.html', template_values)
    elif action == 'view':
      #set the admin user as primary curator, and delete all access to him in
      #db.
      organization = models.Organization.get(self.request.get('org_key'))
      user = models.User().get_by_key_name(self.username)
      allmyrights = models.UserAccessRights.all().filter(
          'username', user).fetch(100)
      models.db.delete(allmyrights)
      models.UserAccessRights(
          username=user, organization=organization).put()
      self.redirect('/curate_videos')

  def post(self):
    action = self.request.get('action')
    if action == 'toggle_status':
      status = self.request.get('status')
      organization = models.Organization.get(self.request.get('org_key'))
      if organization:
        organization.status = status
        organization.put()

    else:
      organisation = self.request.get('campaign_name')
      curator_list = self.request.get('curator_list')
      partner_list = self.request.get('partner_list')
      playlistids = self.request.get('playlist_ids')
      
      organization = models.Organization(
          name=self.request.get('campaign_name'),
          description=self.request.get('description'))
      organization.put()
      taskqueue.add(url='/admin/addcurators',
                    params={'org_key': organization.key(), 
                            'curators': curator_list})
      taskqueue.add(url='/admin/addpartners',
                    params={'org_key': organization.key(),
                            'channel_names': partner_list})
    self.redirect('/admin/')

  
class OrganizationTags(BaseAdminHandler):
  def __init__(self):
    BaseAdminHandler.__init__(self)

  def GetRequest(self):
    organization = models.Organization.get(self.request.get('org_key'))
    tags = models.Tags.all().filter('organization', organization)
    template_values = {'tags': tags, 'organization': organization}
    self.Render('edit_tags.html', template_values)

  def post(self):
    organization = models.Organization.get(self.request.get('org_key'))
    associate_to_playlist = True if self.request.get('assoc_to_pl') else False
    tag_name = self.request.get('tag_name')
    action = self.request.get('action', 'add')
    key_name = '%s_%s' % (organization.key(), tag_name)
    if action == 'add':
      playlistid_to_assign = None
      #do data dump if WhiteLabelPlaylistBuckets is empty
      if models.WhiteLabelPlaylistBuckets.all().count() == 0:
        taskqueue.add(url='/admin/dump_playlists', params={})

      if associate_to_playlist:
        spare_pl = models.WhiteLabelPlaylistBuckets.all().filter(
            'is_available', True).get()
        if spare_pl:
          spare_pl.is_available = False
          spare_pl.put()
          playlistid_to_assign = spare_pl.playlistid

      models.Tags(
          key_name=key_name,
          organization=organization,
          playlistid=playlistid_to_assign,
          name=tag_name,
          associate_to_playlist=associate_to_playlist).put()
    elif action == 'delete':
      tag = models.Tags().get_by_key_name(key_name)
      models.db.delete(tag)

    memcache.delete('%s_tags' % organization.key())
    self.redirect(
        '/admin/manage_organization_tags?org_key=%s' % organization.key())
    

def UpdateUserVideos(entity):
  """Map reduce worker to process or refresh YT video data for each uploader.

  Args:
    entity: models.PremiumPartners entity.
  """
  entity.last_update = today = datetime.datetime.now()
  entity.num_of_new_videos = 0
  try:
    videos, _ = utils.YTVideoFetcher(entity.channel_name)
  except Exception, e:
    logging.info(e)
    entity.alerts = 'Broken partner - failed to fetch video data'
    videos = []

  for video_meta in videos:
    # checking cache to avoid hitting mysql server
    if memcache.get(video_meta['videoid']):
      continue

    entity.num_of_new_videos += 1
    #store all videos ids into datstore for later checking of deletion.
    models.VideosToVerify().get_or_insert(
        key_name=video_meta['videoid'],
        videoid=video_meta['videoid'],
        last_checked=today)

    models.Videos().InsertVideos(video_meta)
    memcache.set(video_meta['videoid'], True)

  yield op.db.Put(entity)


def GetPlaylistVideos(entity):
  """Map reduce worker to fetch YT video data for playlists.

  Args:
    entity: models.Playlists entity.
  """
  entity.last_updated = today = datetime.datetime.now()
  entity.num_of_new_videos = 0

  try:
    videos = utils.GetVideoFeed(entity.playlistid)
  except Exception, e:
    logging.info(e)
    entity.alerts = 'Broken playlistid - failed to fetch any videos'

  for video_meta in videos:

    # checking cache to avoid hitting mysql server
    if memcache.get(video_meta['videoid']):
      continue
    entity.num_of_new_videos += 1
    entity.videotext += '%s,' % video_meta['videoid']
    #store all videos ids into datstore for later checking of deletion.
    models.VideosToVerify().get_or_insert(
        key_name=video_meta['videoid'],
        videoid=video_meta['videoid'],
        last_checked=today)

    _ = models.Videos().InsertVideos(video_meta)

    memcache.set(video_meta['videoid'], True)

  yield op.db.Put(entity)


class VideoUpdateMapReduce(base_handler.PipelineBase):
  def run(self):
    logging.info('Running YouTube video updater')
    output = yield mapreduce_pipeline.MapreducePipeline(
        "yt_video_updater",
        "admin_handler.UpdateUserVideos",
        "",
        "mapreduce.input_readers.DatastoreInputReader",
        "",
        mapper_params={'entity_kind': 'models.PremiumPartners'},
        shards=10)


class UpdatePartnerVideos(webapp.RequestHandler):
  """Handler to trigger map reduce on http get requests, for cronjobs."""
  def get(self):
   pipeline = VideoUpdateMapReduce()
   pipeline.start()
   logging.info('running partner video update - map reduce')


class UpdatePartnerVideosMeta(webapp.RequestHandler):
  """Handler to trigger map reduce on http get requests, for cronjobs."""
  def get(self):
   pipeline = VideoMetaUpdateMapReduce()
   pipeline.start()
   logging.info('running partner video update - map reduce')


class VideoMetaUpdateMapReduce(base_handler.PipelineBase):
  def run(self):
    logging.info('Running YouTube video updater')
    output = yield mapreduce_pipeline.MapreducePipeline(
        "yt_video_meta_updater",
        "admin_handler.UpdateUserVideosMeta",
        "",
        "mapreduce.input_readers.DatastoreInputReader",
        "",
        mapper_params={'entity_kind': 'models.PremiumPartners'},
        shards=10)


def UpdateUserVideosMeta(entity):
  """Map reduce worker to process or refresh YT video data meta for each uploader.

  This process puts a heavy load on YT API dev key, and also the curationDB.

  Args:
    entity: models.PremiumPartners entity.
  """
  entity.last_updated = datetime.datetime.now()
  videos, videos_to_delete = utils.FetchYTStream(entity.yt_username)
  videos_to_keep = []
  for video_meta in videos:
    update_data = utils.UpdateVideoDict(video_meta)
    videos_to_keep.append(update_data['videoid'])

    if update_data.has_key('published'):
      update_data.pop('published') # dont need to update video published value
    try:
      sql_connector.SQLConnector().UpdateTable(
          set_values=update_data,
          where_filters={'videoid': video_meta['videoid']},
          table='videos')
    except Exception, e:
      #DB locking happens here sometimes, causing MR to hang in prod
      logging.info(e)

  yield op.db.Put(entity)


def VideoDeleteChecker(entity):
  entity.last_checked = datetime.datetime.today()

  try:
    video_meta = utils.FetchVideoData(entity.videoid)
  except:
    logging.info('%s - flagged as deleted', entity.videoid)
    entity.flagged_delete = True
    #TODO: add auto remove from db, and curation
    data = {'videoid': entity.videoid}
    sql_connector.SQLConnector().DeleteData(data, 'videos')
    sql_connector.SQLConnector().DeleteData(data, 'curated_videos')
    models.DeletedVideos(videoid=entity.videoid).put()
    yield op.db.Delete(entity)

  yield op.db.Put(entity)

class VideoDeleteCheckerHandler(webapp.RequestHandler):
  """Handler to trigger map reduce on http get requests, for cronjobs."""
  def get(self):
   pipeline = VideoDeleteCheckerMapReduce()
   pipeline.start()
   logging.info('running video deleted checker - map reduce')


class VideoDeleteCheckerMapReduce(base_handler.PipelineBase):
  def run(self):
    logging.info('Running video delete checker')
    output = yield mapreduce_pipeline.MapreducePipeline(
        "video_delete_checker",
        "admin_handler.VideoDeleteChecker",
        "",
        "mapreduce.input_readers.DatastoreInputReader",
        "",
        mapper_params={'entity_kind': 'models.VideosToVerify'},
        shards=10)


def main():
  application = webapp.WSGIApplication([
      ('/admin/?$', AdminIndexHandler),
      ('/admin/addcurators', AddCurators),
      ('/admin/addpartners', AddPartners),
      ('/admin/addplaylists', AddPlaylists),
      # MapReduce URL Triggers
      ('/admin/video_deleted_checker', VideoDeleteCheckerHandler),
      # end Triggers

      ('/admin/dump_playlists', DumpYTPlaylists),
      ('/admin/manage_organization_tags', OrganizationTags),
      ('/admin/manage_organization', Organization),
      ], debug=True)
  util.run_wsgi_app(application)


if __name__ == '__main__':
  main()
